-
Notifications
You must be signed in to change notification settings - Fork 17.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
x/crypto/ssh/terminal: ReadPassword doesn't work on redirected stdin giving inappropriate ioctl for device #19909
Comments
was this working before in a previous go release? |
@jessfraz I don't think this has ever worked - I did a quick bit of testing with old go versions and old x/crypto versions and couldn't find a working version. |
hmmm odd will play around |
Are there any updates on this issue?
Gives me the same error: |
You can try some istty handling like: if (stat.Mode() & os.ModeCharDevice) == 0 {
reader := bufio.NewReader(os.Stdin)
pass, err = reader.ReadString('\n')
if err != nil {
log.Fatalf("Error when typing password: %s", err.Error())
}
} else {
//old prompt code
... |
When I use standard input for a password, I always check if it is a terminal and handle the two cases separately, like this: fd := int(os.Stdin.Fd())
if terminal.IsTerminal(fd) {
pw, err = terminal.ReadPassword(fd)
} else {
// handle non-terminal
}
Even if it did work with non-terminal FDs, I wouldn't rely on undocumented behavior. This looks like a feature request. |
@ncw the only solution I know is to open the tty and read from it, something like: func readPassword(prompt string) ([]byte, error) {
fmt.Fprint(os.Stderr, prompt)
var fd int
if terminal.IsTerminal(syscall.Stdin) {
fd = syscall.Stdin
} else {
tty, err := os.Open("/dev/tty")
if err != nil {
return nil, errors.Wrap(err, "error allocating terminal")
}
defer tty.Close()
fd = int(tty.Fd())
}
pass, err := terminal.ReadPassword(fd)
fmt.Fprintln(os.Stderr)
return pass, err
} |
I can think of two common needs:
$ cat input-file | myprogram # read password from terminal, not pipe
Password: _
$ cat input-file | myprogram # read password from pipe
$
$ myprogram # read password from terminal
Password: _ In case In case @ncw from the original post I would understand you are trying to achieve |
@pam4 - I guess I was expecting If it worked like option 1) that would be fine too. However just returning Perhaps a patch to the documentation and to the the error returned might be all that is needed: @@ -93,11 +95,12 @@ func (r passwordReader) Read(buf []byte) (int, error) {
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
-// returned does not include the \n.
+// returned does not include the \n. Note that ReadPassword will only work on
+// an fd where IsTerminal(fd) returns true.
func ReadPassword(fd int) ([]byte, error) {
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("input must be a terminal: %v", err)
}
newState := *termios |
@pam4 @ncw: my example was for case It's not a common case, and probably not a good practice to get the user password from STDIN. For example, you can't do that using ssh ( If you need to automatize something a most common case would be to use environment variables, a named file from an argument or an external secrets management tool API. |
You can still use all of these with the command line, without needing to make your Go code support them. E.g.:
|
I don't think it is so uncommon. For example the ecryptfs utils read passphrases from Reading a password from Of course it can be done wrong, for example if the password itself end up as a process argument, it would be very insecure (this is the case of But I think any method to exchange secrets between processes can be used in an insecure way, unless it is done with care by someone who knows all the subtleties involved. Going into details about this is probably off topic, but the point is that it is not always handled the same way, even among well known tools. @ncw |
If you look at the implementation of So I don't see the point of calling |
@ncw you are right, I should have checked, I assumed |
I end up to this situation too. My specific case is exactly like func readPassword(prompt string) ([]byte, error) {
fmt.Fprint(os.Stderr, prompt)
var fd int
var pass []byte
if terminal.IsTerminal(syscall.Stdin) {
fd = syscall.Stdin
inputPass, err := terminal.ReadPassword(fd)
if err != nil {
return nil, err
}
pass = inputPass
} else {
reader := bufio.NewReader(os.Stdin)
s, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
pass = []byte(s)
}
return pass, nil
} |
@truongnmt your solution may be fine for your use case, but be aware that the buffering will generally prevent further use of standard input. For example suppose that you need to read two passwords. Calling your Here's a variation that doesn't have the buffering problem: func readPassword(prompt string) (pw []byte, err error) {
fd := int(os.Stdin.Fd())
if terminal.IsTerminal(fd) {
fmt.Fprint(os.Stderr, prompt)
pw, err = terminal.ReadPassword(fd)
fmt.Fprintln(os.Stderr)
return
}
var b [1]byte
for {
n, err := os.Stdin.Read(b[:])
// terminal.ReadPassword discards any '\r', so we do the same
if n > 0 && b[0] != '\r' {
if b[0] == '\n' {
return pw, nil
}
pw = append(pw, b[0])
// limit size, so that a wrong input won't fill up the memory
if len(pw) > 1024 {
err = errors.New("password too long")
}
}
if err != nil {
// terminal.ReadPassword accepts EOF-terminated passwords
// if non-empty, so we do the same
if err == io.EOF && len(pw) > 0 {
err = nil
}
return pw, err
}
}
} @truongnmt you also forgot to trim the final |
Thanks for such a detail reply. Hmm I also use the custom |
@truongnmt The For example: $ echo -en 'password1\npassword2\n' | yourprogram # probably fails
$ cat passwordfile
password1
password2
$ cat passwordfile | yourprogram # probably fails
$ { echo password1; sleep 1; echo password2; } | yourprogram # probably works Here is a playground demonstration: Notice that |
@ncw, at first I didn't like the idea of changing Ok, I would expect a function called The code could be changed from this: termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
return nil, err
}
// set up the terminal
// do the read to this: termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err == nil {
// set up the terminal
}
// do the read Users that only want a terminal could still use the Possible problems:
|
fmt.Print("Encryption-key: ")
pwd, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return err
}
fmt.Print("\n") |
I am not involved in the terminal package. |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as off-topic.
This comment was marked as off-topic.
Hello there, when terminal package is called on init function inside a large project, it createas a |
removed this from my program, and now it works tem x package also was automatically removed from go.mod func init(){ |
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?go version go1.8 linux/amd64
What operating system and processor architecture are you using (
go env
)?GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/ncw/go"
GORACE=""
GOROOT="/opt/go/go1.8"
GOTOOLDIR="/opt/go/go1.8/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build011975399=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
What did you do?
Use ReadPassword with redirected stdin - it gives error "inappropriate ioctl for device"
Save this code as readpass.go
What did you expect to see?
I would expect ReadPass to figure out that it is not reading from a terminal before issuing ioctls that are terminal specific.
What did you see instead?
Originally reported in: rclone/rclone#1308
The text was updated successfully, but these errors were encountered: