Added pty lib to give true terminal capability. raw mode/restore for client working

This commit is contained in:
Russ Magee 2018-01-18 18:57:37 -08:00
parent e8fe31f6d7
commit 49c589ee8d
3 changed files with 142 additions and 7 deletions

View file

@ -10,6 +10,7 @@ import (
"sync"
hkex "blitter.com/herradurakex"
"golang.org/x/sys/unix"
)
// Demo of a simple client that dials up to a simple test server to
@ -45,6 +46,13 @@ func main() {
}
defer conn.Close()
// Set stdin in raw mode.
oldState, err := MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
wg.Add(1)
go func() {
// This will guarantee the side that closes first
@ -64,7 +72,7 @@ func main() {
os.Exit(1)
}
}
fmt.Println("[Got Write EOF]")
log.Println("[Got Write EOF]")
wg.Done() // client hanging up, close server read goroutine
}()
@ -81,10 +89,68 @@ func main() {
os.Exit(2)
}
}
fmt.Println("[Got Read EOF]")
log.Println("[Got Read EOF]")
wg.Done() // server hung up, close client write goroutine
}()
// Wait until both stdin and stdout goroutines finish
wg.Wait()
}
/* ------------- minimal terminal APIs brought in from ssh/terminal
* (they have no real business being there as they aren't specific to
* ssh.)
* -------------
*/
const ioctlReadTermios = unix.TCGETS
const ioctlWriteTermios = unix.TCSETS
// 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(fd int) (*State, error) {
termios, err := unix.IoctlGetTermios(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(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(fd int) (*State, error) {
termios, err := unix.IoctlGetTermios(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(fd int, state *State) error {
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
}

View file

@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os/exec"
@ -12,6 +13,7 @@ import (
"time"
hkex "blitter.com/herradurakex"
"github.com/kr/pty"
)
const (
@ -37,6 +39,60 @@ type cmdRunner struct {
status int
}
/* ------------- minimal terminal APIs brought in from ssh/terminal
* (they have no real business being there as they aren't specific to
* ssh.)
* -------------
*/
/*
// 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(fd int) (*State, error) {
termios, err := unix.IoctlGetTermios(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(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(fd int) (*State, error) {
termios, err := unix.IoctlGetTermios(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(fd int, state *State) error {
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
}
*/
/* -------------------------------------------------------------- */
/*
func cmd(r *cmdRunner) {
switch r.op {
@ -72,7 +128,19 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
c.Stdout = conn
c.Stderr = conn
err = c.Run()
// Start the command with a pty.
ptmx, err := pty.Start(c) // returns immediately with ptmx file
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
// Copy stdin to the pty and the pty to stdout.
go func() { _, _ = io.Copy(ptmx, conn) }()
_, _ = io.Copy(conn, ptmx)
//err = c.Run() // returns when c finishes.
if err != nil {
log.Printf("Command finished with error: %v", err)
log.Printf("[%s]\n", cmd)
@ -175,6 +243,7 @@ func main() {
// Clear current op so user can enter next, or EOF
connOp = nil
fmt.Println("[Exiting shell]")
conn.Close()
}
if strings.Trim(string(data), "\r\n") == "exit" {
conn.Close()

View file

@ -175,7 +175,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e
// Close a hkex.Conn
func (c Conn) Close() (err error) {
err = c.c.Close()
fmt.Println("[Conn Closing]")
log.Println("[Conn Closing]")
return
}
@ -241,7 +241,7 @@ func Listen(protocol string, ipport string) (hl HKExListener, e error) {
if err != nil {
return HKExListener{nil}, err
}
fmt.Println("[Listening]")
log.Println("[Listening]")
hl.l = l
return
}
@ -250,7 +250,7 @@ func Listen(protocol string, ipport string) (hl HKExListener, e error) {
//
// See go doc io.Close
func (hl HKExListener) Close() error {
fmt.Println("[Listener Closed]")
log.Println("[Listener Closed]")
return hl.l.Close()
}
@ -263,7 +263,7 @@ func (hl HKExListener) Accept() (hc Conn, err error) {
return Conn{c: nil, h: nil, cipheropts: 0, opts: 0,
r: nil, w: nil}, err
}
fmt.Println("[Accepted]")
log.Println("[Accepted]")
hc = Conn{c: c, h: New(0, 0), cipheropts: 0, opts: 0, op: 0, r: nil, w: nil}