diff --git a/go.mod b/go.mod index a12ada7c8..4ace29fb2 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/spf13/pflag v1.0.5 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 ) require ( diff --git a/go.sum b/go.sum index 5eb1ff929..01dee9cfe 100644 --- a/go.sum +++ b/go.sum @@ -16,10 +16,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/tools/cmd/at/main.go b/tools/cmd/at/main.go index efb5c23f0..99b971a05 100644 --- a/tools/cmd/at/main.go +++ b/tools/cmd/at/main.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "golang.org/x/sys/unix" - "golang.org/x/term" "kitty" "kitty/tools/base85" @@ -188,10 +187,7 @@ func get_response(rc *utils.RemoteControlCmd, timeout float64) (ans *Response, e if err != nil { return } - defer func() { - term.Restore() - term.Close() - }() + defer term.RestoreAndClose() device = term } else { err = fmt.Errorf("TODO: Implement socket IO") @@ -254,27 +250,33 @@ func get_password(password string, password_file string, password_env string, us if ans == "" && password_file != "" { if password_file == "-" { if tty.IsTerminal(os.Stdin.Fd()) { - q, err := term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { + var q string + q, err = tty.ReadPassword("Password: ") + if err == nil { ans = string(q) } } else { - q, err := io.ReadAll(os.Stdin) - if err != nil { + var q []byte + q, err = io.ReadAll(os.Stdin) + if err == nil { ans = strings.TrimRight(string(q), " \n\t") } - ttyf, err := os.Open("/dev/tty") - if err != nil { + ttyf, err := os.Open(tty.Ctermid()) + if err == nil { err = unix.Dup2(int(ttyf.Fd()), int(os.Stdin.Fd())) ttyf.Close() } } } else { - q, err := os.ReadFile(password_file) - if err != nil { + var q []byte + q, err = os.ReadFile(password_file) + if err == nil { ans = strings.TrimRight(string(q), " \n\t") } } + if err != nil { + return + } } if ans == "" && password_env != "" { ans = os.Getenv(password_env) diff --git a/tools/tty/tty.go b/tools/tty/tty.go index 4221c2112..0d7b221d9 100644 --- a/tools/tty/tty.go +++ b/tools/tty/tty.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "runtime" "time" "golang.org/x/sys/unix" @@ -90,15 +91,17 @@ var SetNoEcho TermiosOperation = func(t *unix.Termios) { t.Lflag &^= unix.ECHO } -func OpenTerm(name string, operations ...TermiosOperation) (self *Term, err error) { - fd, err := eintr_retry_intret(func() (int, error) { - return unix.Open(name, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666) - }) - if err != nil { - return nil, &os.PathError{Op: "open", Path: name, Err: err} - } +var SetReadPassword TermiosOperation = func(t *unix.Termios) { + t.Lflag &^= unix.ECHO + t.Lflag |= unix.ISIG + t.Lflag &^= unix.ICANON + t.Iflag |= unix.ICRNL + t.Cc[unix.VMIN] = 1 + t.Cc[unix.VTIME] = 0 +} - self = &Term{name: name, fd: fd} +func WrapTerm(fd int, operations ...TermiosOperation) (self *Term, err error) { + self = &Term{name: fmt.Sprintf("", fd), fd: fd} err = eintr_retry_noret(func() error { return unix.SetNonblock(self.fd, false) }) if err == nil { err = self.ApplyOperations(TCSANOW, operations...) @@ -110,8 +113,22 @@ func OpenTerm(name string, operations ...TermiosOperation) (self *Term, err erro return } +func OpenTerm(name string, operations ...TermiosOperation) (self *Term, err error) { + fd, err := eintr_retry_intret(func() (int, error) { + return unix.Open(name, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666) + }) + if err != nil { + return nil, &os.PathError{Op: "open", Path: name, Err: err} + } + self, err = WrapTerm(fd, operations...) + if err != nil { + self.name = name + } + return +} + func OpenControllingTerm(operations ...TermiosOperation) (self *Term, err error) { - return OpenTerm("/dev/tty", operations...) // go doesnt have a wrapper for ctermid() + return OpenTerm(Ctermid(), operations...) } func (self *Term) Fd() int { return self.fd } @@ -178,6 +195,11 @@ func (self *Term) Restore() error { return self.RestoreWhen(TCSAFLUSH) } +func (self *Term) RestoreAndClose() error { + self.Restore() + return self.Close() +} + func clamp(v, lo, hi int64) int64 { if v < lo { return lo @@ -326,3 +348,78 @@ func (self *Term) GetSize() (*unix.Winsize, error) { } } } + +func (self *Term) read_line(in_password_mode bool) ([]byte, error) { + var buf [1]byte + var ret []byte + + for { + n, err := self.Read(buf[:]) + if n > 0 { + switch buf[0] { + case '\b', 0x7f: + if len(ret) > 0 { + ret = ret[:len(ret)-1] + _, err = self.WriteAllWithTimeout([]byte("\x08\x1b[P"), 5*time.Second) + if err != nil { + return nil, err + } + } + case '\n': + if runtime.GOOS != "windows" { + return ret, nil + } + // otherwise ignore \n + case '\r': + if runtime.GOOS == "windows" { + return ret, nil + } + // otherwise ignore \r + default: + ret = append(ret, buf[0]) + _, err = self.WriteAllWithTimeout([]byte("*"), 5*time.Second) + if err != nil { + return nil, err + } + } + continue + } + if err != nil { + if err == io.EOF && len(ret) > 0 { + return ret, nil + } + return nil, err + } + } +} + +func (self *Term) ReadLine() ([]byte, error) { + return self.read_line(false) +} + +func (self *Term) ReadPassword() (string, error) { + pw, err := self.read_line(false) + return string(pw), err +} + +// go doesnt have a wrapper for ctermid() +func Ctermid() string { return "/dev/tty" } + +func ReadPassword(prompt string) (string, error) { + term, err := OpenControllingTerm(SetReadPassword) + if err != nil { + return "", err + } + defer term.RestoreAndClose() + if len(prompt) > 0 { + _, err = term.WriteAllWithTimeout([]byte(prompt), 5*time.Second) + if err != nil { + return "", err + } + } + pw, err := term.ReadPassword() + if err != nil { + return "", err + } + return pw, nil +}