// +build linux package xs import ( "errors" "io" "os" unix "golang.org/x/sys/unix" ) /* ------------- * minimal terminal APIs brought in from ssh/terminal * (they have no real business being there as they aren't specific to * ssh, but as of Go v1.10, late 2019, core go stdlib hasn't yet done * the planned terminal lib reorgs.) * ------------- */ // From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go const ioctlReadTermios = unix.TCGETS const ioctlWriteTermios = unix.TCSETS // From github.com/golang/crypto/blob/master/ssh/terminal/util.go // State contains the state of a terminal. type State struct { termios unix.Termios } // MakeRaw put the terminal connected to the given file descriptor into raw // mode and returns the previous state of the terminal so that it can be // restored. func MakeRaw(f *os.File) (*State, error) { fd := f.Fd() termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) if err != nil { return nil, err } oldState := State{termios: *termios} // This attempts to replicate the behaviour documented for cfmakeraw in // the termios(3) manpage. termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON termios.Oflag &^= unix.OPOST termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN termios.Cflag &^= unix.CSIZE | unix.PARENB termios.Cflag |= unix.CS8 termios.Cc[unix.VMIN] = 1 termios.Cc[unix.VTIME] = 0 if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil { return nil, err } return &oldState, nil } // GetState returns the current state of a terminal which may be useful to // restore the terminal after a signal. func GetState(f *os.File) (*State, error) { termios, err := unix.IoctlGetTermios(int(f.Fd()), ioctlReadTermios) if err != nil { return nil, err } return &State{termios: *termios}, nil } // Restore restores the terminal connected to the given file descriptor to a // previous state. func Restore(f *os.File, state *State) error { if state != nil { return unix.IoctlSetTermios(int(f.Fd()), ioctlWriteTermios, &state.termios) } else { return errors.New("nil State") } } // 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. func ReadPassword(f *os.File) ([]byte, error) { fd := f.Fd() termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) if err != nil { return nil, err } newState := *termios newState.Lflag &^= unix.ECHO newState.Lflag |= unix.ICANON | unix.ISIG newState.Iflag |= unix.ICRNL if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil { return nil, err } defer func() { _ = unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) // nolint: gosec }() return readPasswordLine(passwordReader(fd)) } // passwordReader is an io.Reader that reads from a specific file descriptor. type passwordReader int func (r passwordReader) Read(buf []byte) (int, error) { return unix.Read(int(r), buf) } // readPasswordLine reads from reader until it finds \n or io.EOF. // The slice returned does not include the \n. // readPasswordLine also ignores any \r it finds. func readPasswordLine(reader io.Reader) ([]byte, error) { var buf [1]byte var ret []byte for { n, err := reader.Read(buf[:]) if n > 0 { switch buf[0] { case '\n': return ret, nil case '\r': // remove \r from passwords on Windows default: ret = append(ret, buf[0]) } continue } if err != nil { if err == io.EOF && len(ret) > 0 { return ret, nil } return ret, err } } }