diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 57a6e4cc8..bbb32624e 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -10,13 +10,13 @@ import ( "path/filepath" "strings" + "github.com/containers/common/pkg/util" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/pkg/docker/config" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/types" "github.com/sirupsen/logrus" - terminal "golang.org/x/term" ) // ErrNewCredentialsInvalid means that the new user-provided credentials are @@ -269,7 +269,7 @@ func getUserAndPass(opts *LoginOptions, password, userFromAuthFile string) (user } if password == "" { fmt.Fprint(opts.Stdout, "Password: ") - pass, err := terminal.ReadPassword(int(os.Stdin.Fd())) + pass, err := util.ReadPassword(int(os.Stdin.Fd())) if err != nil { return "", "", fmt.Errorf("reading password: %w", err) } diff --git a/pkg/util/util.go b/pkg/util/util.go index dd79f91fb..516422f09 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -2,6 +2,7 @@ package util import ( "bytes" + "errors" "fmt" "os" "os/exec" @@ -19,6 +20,10 @@ const ( UnknownPackage = "Unknown" ) +var ( + ErrInterrupt = errors.New("Interrupt") +) + // Note: This function is copied from containers/podman libpod/util.go // Please see https://github.com/containers/common/pull/1460 func queryPackageVersion(cmdArg ...string) string { diff --git a/pkg/util/util_supported.go b/pkg/util/util_supported.go index 0cd53af53..aa659c17e 100644 --- a/pkg/util/util_supported.go +++ b/pkg/util/util_supported.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "os" + "os/signal" "path/filepath" "sync" "syscall" @@ -14,6 +15,7 @@ import ( "github.com/containers/storage/pkg/homedir" "github.com/containers/storage/pkg/unshare" "github.com/sirupsen/logrus" + terminal "golang.org/x/term" ) var ( @@ -89,3 +91,45 @@ func GetRuntimeDir() (string, error) { } return rootlessRuntimeDir, nil } + +// ReadPassword reads a password from the terminal without echo. +func ReadPassword(fd int) ([]byte, error) { + // Store and restore the terminal status on interruptions to + // avoid that the terminal remains in the password state + // This is necessary as for https://github.com/golang/go/issues/31180 + + oldState, err := terminal.GetState(fd) + if err != nil { + return make([]byte, 0), err + } + + type Buffer struct { + Buffer []byte + Error error + } + errorChannel := make(chan Buffer, 1) + + // SIGINT and SIGTERM restore the terminal, otherwise the no-echo mode would remain intact + interruptChannel := make(chan os.Signal, 1) + signal.Notify(interruptChannel, syscall.SIGINT, syscall.SIGTERM) + defer func() { + signal.Stop(interruptChannel) + close(interruptChannel) + }() + go func() { + for range interruptChannel { + if oldState != nil { + _ = terminal.Restore(fd, oldState) + } + errorChannel <- Buffer{Buffer: make([]byte, 0), Error: ErrInterrupt} + } + }() + + go func() { + buf, err := terminal.ReadPassword(fd) + errorChannel <- Buffer{Buffer: buf, Error: err} + }() + + buf := <-errorChannel + return buf.Buffer, buf.Error +} diff --git a/pkg/util/util_windows.go b/pkg/util/util_windows.go index 1525bdc34..cc431e8ad 100644 --- a/pkg/util/util_windows.go +++ b/pkg/util/util_windows.go @@ -5,9 +5,24 @@ package util import ( "errors" + + terminal "golang.org/x/term" ) // getRuntimeDir returns the runtime directory func GetRuntimeDir() (string, error) { return "", errors.New("this function is not implemented for windows") } + +// ReadPassword reads a password from the terminal. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := terminal.GetState(fd) + if err != nil { + return make([]byte, 0), err + } + buf, err := terminal.ReadPassword(fd) + if oldState != nil { + _ = terminal.Restore(fd, oldState) + } + return buf, err +}