mirror of https://gogs.blitter.com/RLabs/xs
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:
parent
49c589ee8d
commit
732005d9bf
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue