diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 85af82962..9ab53cf5f 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" ) // GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default @@ -244,12 +244,12 @@ 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) } - password = string(pass) fmt.Fprintln(opts.Stdout) + password = string(pass) } return strings.TrimSpace(username), password, err } diff --git a/pkg/util/util.go b/pkg/util/util.go index 98890a686..96cc1c791 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,6 +1,14 @@ package util -import "regexp" +import ( + "fmt" + "os" + "os/signal" + "regexp" + "syscall" + + "golang.org/x/term" +) // StringInSlice determines if a string is in a string slice, returns bool func StringInSlice(s string, sl []string) bool { @@ -22,3 +30,68 @@ func StringMatchRegexSlice(s string, re []string) bool { } return false } + +// ReadPassword reads a password from the terminal and restores the terminal state on interruption +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 + + /* WARNING: This commit contains two suggestions and should as such not be commited !! */ + reemitSolution := true + + /* Suggestion 1: Re-emit the interrupt signal, but restore the terminal in-between*/ + if reemitSolution { + oldState, _ := term.GetState(fd) + interruptChannel := make(chan os.Signal, 1) + signal.Notify(interruptChannel, os.Interrupt) + defer func() { + signal.Stop(interruptChannel) + close(interruptChannel) + }() + go func() { + for range interruptChannel { + if oldState != nil { + term.Restore(fd, oldState) + } + // Call the default SIGINT handler + signal.Reset(os.Interrupt) + syscall.Kill(syscall.Getpid(), syscall.SIGINT) + } + }() + return term.ReadPassword(fd) + } else { + /* Suggestion 2: Gracefully handle the interrupt signal, but leave the terminal input blocked */ + + type ByteBuffer struct { + buf []byte + err error + } + + oldState, _ := term.GetState(fd) + interruptChannel := make(chan os.Signal, 1) + signal.Notify(interruptChannel, os.Interrupt) + defer func() { + signal.Stop(interruptChannel) + close(interruptChannel) + }() + + // Read from the file descriptor or wait for the interrupt signal + inputChannel := make(chan ByteBuffer, 1) + defer close(inputChannel) + go func() { + var buf ByteBuffer + buf.buf, buf.err = term.ReadPassword(fd) + inputChannel <- buf + }() + select { + case <-interruptChannel: + if oldState != nil { + term.Restore(fd, oldState) + } + return make([]byte, 0), fmt.Errorf("interrupted") + case buf := <-inputChannel: + return buf.buf, buf.err + } + } +}