Skip to content

Commit

Permalink
Add restore handler for password input
Browse files Browse the repository at this point in the history
This commit restores the terminal state in case the program is
interrupted while being in password read mode. This ensures the terminal
remains usable, also if the password input is being cancelled.

Signed-off-by: Felix Niederwanger <[email protected]>
  • Loading branch information
grisu48 committed Feb 8, 2023
1 parent 17f7e40 commit 828badb
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 4 deletions.
6 changes: 3 additions & 3 deletions pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
75 changes: 74 additions & 1 deletion pkg/util/util.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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
}
}
}

0 comments on commit 828badb

Please sign in to comment.