Some cleanup in prep for possible io.ReadFull() fixed-block session-cmd header to resolve the eaten-byte issue handing Accept off to cmdRunner

This commit is contained in:
Russ Magee 2018-01-18 21:17:57 -08:00
parent 49c589ee8d
commit 732005d9bf
2 changed files with 53 additions and 110 deletions

View File

@ -10,6 +10,7 @@ import (
"sync" "sync"
hkex "blitter.com/herradurakex" hkex "blitter.com/herradurakex"
isatty "github.com/mattn/go-isatty"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -31,6 +32,7 @@ func main() {
var cAlg string var cAlg string
var hAlg string var hAlg string
var server string var server string
isInteractive := false
flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]") flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]")
flag.StringVar(&hAlg, "h", "H_SHA256", "hmac [\"H_SHA256\"]") flag.StringVar(&hAlg, "h", "H_SHA256", "hmac [\"H_SHA256\"]")
@ -46,20 +48,27 @@ func main() {
} }
defer conn.Close() defer conn.Close()
// Set stdin in raw mode. // Set stdin in raw mode if it's an interactive session
oldState, err := MakeRaw(int(os.Stdin.Fd())) if isatty.IsTerminal(os.Stdin.Fd()) {
if err != nil { isInteractive = true
panic(err) oldState, err := MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
} else {
fmt.Println("NOT A TTY")
} }
defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
wg.Add(1) wg.Add(1)
go func() { go func() {
// This will guarantee the side that closes first // By deferring a call to wg.Done(),
// marks its direction's goroutine as finished. // each goroutine guarantees that it marks
// its direction's stream as finished.
//
// Whichever direction's goroutine finishes first // Whichever direction's goroutine finishes first
// will call wg.Done() once more explicitly to // will call wg.Done() once more, explicitly, to
// hang up on the other side so the client // hang up on the other side, so that this client
// exits immediately on an EOF from either side. // exits immediately on an EOF from either side.
defer wg.Done() defer wg.Done()
@ -72,8 +81,10 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
log.Println("[Got Write EOF]") if isInteractive {
wg.Done() // client hanging up, close server read goroutine log.Println("[Got Write EOF]")
wg.Done() // client hanging up, close WaitGroup to exit client
}
}() }()
wg.Add(1) wg.Add(1)
@ -90,7 +101,7 @@ func main() {
} }
} }
log.Println("[Got Read EOF]") log.Println("[Got Read EOF]")
wg.Done() // server hung up, close client write goroutine wg.Done() // server hung up, close WaitGroup to exit client
}() }()
// Wait until both stdin and stdout goroutines finish // Wait until both stdin and stdout goroutines finish
@ -99,13 +110,16 @@ func main() {
/* ------------- minimal terminal APIs brought in from ssh/terminal /* ------------- minimal terminal APIs brought in from ssh/terminal
* (they have no real business being there as they aren't specific to * (they have no real business being there as they aren't specific to
* ssh.) * ssh, but as of v1.10, early 2018, 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 ioctlReadTermios = unix.TCGETS
const ioctlWriteTermios = unix.TCSETS const ioctlWriteTermios = unix.TCSETS
// From github.com/golang/crypto/blob/master/ssh/terminal/util.go
// State contains the state of a terminal. // State contains the state of a terminal.
type State struct { type State struct {
termios unix.Termios termios unix.Termios

View File

@ -16,6 +16,9 @@ import (
"github.com/kr/pty" "github.com/kr/pty"
) )
// Unused, probably obsolete. Once interactive session
// and piped I/O one-shot commands are working reconsider
// how Op might be used
const ( const (
OpR = 'r' // read(file) (binary mode) OpR = 'r' // read(file) (binary mode)
OpW = 'w' // (over)write OpW = 'w' // (over)write
@ -39,78 +42,11 @@ type cmdRunner struct {
status int 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 {
case OpR:
//Clean up r.cmd beforehand
r.arg = strings.TrimSpace(r.arg)
fmt.Printf("[cmd was:'%s']\n", r.arg)
runCmdAs(r.who, r.arg, nil)
fmt.Println(r.arg)
break
default:
fmt.Printf("[cmd %d ignored:%d]\n", int(r.op))
break
}
}
*/
// Run a command (via os.exec) as a specific user // Run a command (via os.exec) as a specific user
//
// Uses ptys to support commands which expect a terminal.
func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) { func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
u, _ := user.Lookup(who) u, _ := user.Lookup(who)
var uid, gid uint32 var uid, gid uint32
@ -183,6 +119,7 @@ func main() {
// multiple connections may be served concurrently. // multiple connections may be served concurrently.
go func(c hkex.Conn) (e error) { go func(c hkex.Conn) (e error) {
defer c.Close() defer c.Close()
var connOp *byte = nil
ch := make(chan []byte) ch := make(chan []byte)
chN := 0 chN := 0
eCh := make(chan error) eCh := make(chan error)
@ -204,8 +141,6 @@ func main() {
}(ch, eCh) }(ch, eCh)
ticker := time.Tick(time.Second / 100) ticker := time.Tick(time.Second / 100)
//var r cmdRunner
var connOp *byte = nil
Term: Term:
// continuously read from the connection // continuously read from the connection
for { for {
@ -216,39 +151,33 @@ func main() {
fmt.Printf("Client sent %+v\n", data[0:chN]) fmt.Printf("Client sent %+v\n", data[0:chN])
if connOp == nil { if connOp == nil {
// Initial xmit - get op byte // Initial xmit - get op byte
// (TODO: determine valid ops // Have op here and first block of data[]
// for now 'e' (echo), 'i' (interactive), 'x' (exec), ... ?)
connOp = new(byte) connOp = new(byte)
*connOp = data[0] *connOp = data[0]
fmt.Printf("[* connOp '%c']\n", *connOp)
}
if len(data) > 1 {
data = data[1:chN] data = data[1:chN]
chN -= 1 chN -= 1
// Have op here and first block of data[]
fmt.Printf("[* connOp '%c']\n", *connOp)
// The CloseHandler typically handles the
// accumulated command data
//r = cmdRunner{op: Op(*connOp),
// who: "larissa", arg: string(data),
// authCookie: "c00ki3",
// status: 0}
} }
// From here, one could pass all subsequent data if len(data) > 0 {
// between client/server attached to an exec.Cmd, // From here, one could pass all subsequent data
// as data to/from a file, etc. // between client/server attached to an exec.Cmd,
if *connOp == 's' { // as data to/from a file, etc.
fmt.Println("[Running shell]") if connOp != nil && *connOp == 's' {
runCmdAs("larissa", "bash -l -i", conn) fmt.Println("[Running shell]")
// Returned hopefully via an EOF or exit/logout; runCmdAs("larissa", "bash -l -i", conn)
// Clear current op so user can enter next, or EOF // Returned hopefully via an EOF or exit/logout;
connOp = nil // Clear current op so user can enter next, or EOF
fmt.Println("[Exiting shell]") connOp = nil
conn.Close() fmt.Println("[Exiting shell]")
conn.Close()
}
if strings.Trim(string(data), "\r\n") == "exit" {
conn.Close()
}
} }
if strings.Trim(string(data), "\r\n") == "exit" {
conn.Close()
}
//fmt.Printf("Client sent %s\n", string(data)) //fmt.Printf("Client sent %s\n", string(data))
// This case means we got an error and the goroutine has finished // This case means we got an error and the goroutine has finished
case err := <-eCh: case err := <-eCh: